/*
 * @Author: duapple
 * @Date: 2021-10-20 17:38:47
 * @LastEditTime: 2021-10-27 15:11:32
 * @LastEditors: duapple
 * @Description: Add description
 * @FilePath: \dfu_file_create\main.go
 * Copyright (c) 2011 duapple - All rights reserved.
 */
package main

import (
	"bufio"
	"bytes"
	"encoding/binary"
	"encoding/json"
	"errors"
	"fmt"
	log "fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
)

type NCPTargetVersion struct {
	NCPVersion      uint16 `json:"ncpVersion"`
	SoftwareVersion uint16 `json:"softwareVersion"`
	HardwareVersion uint16 `json:"hardwareVersion"`
}

type AppConfig struct {
	OtaFile            string           `json:"otaFile"`
	NewOtaFileBaseName string           `json:"newOtaFileBaseName"`
	VersionFile        string           `json:"VersionFile`
	HeaderSize         int              `json:"headerSize"`
	IsSpecifyVersion   bool             `json:"isSpecifyVersion"`
	FileVersion        NCPTargetVersion `json:"fileVersion"`
}

func main() {
	log.Println("create dfu file")

	appConfigContext, err := ioutil.ReadFile("./config.json")
	if err != nil {
		log.Println(err)
		return
	}

	var appConf AppConfig
	err = json.Unmarshal(appConfigContext, &appConf)
	if err != nil {
		log.Println(err)
		return
	}

	// log.Println(appConf)

	var ver = &NCPTargetVersion{0x01, 0x01, 0x01}
	if appConf.IsSpecifyVersion {

	} else {
		if err := appConf.read_version(); err != nil {
			log.Println(err)
			return
		}
	}
	ver.NCPVersion = appConf.FileVersion.NCPVersion
	ver.SoftwareVersion = appConf.FileVersion.SoftwareVersion
	ver.HardwareVersion = appConf.FileVersion.HardwareVersion

	data, err := ver.Encode()
	if err != nil {
		log.Println(err)
		return
	}

	data = bytes_extend(data, appConf.HeaderSize)
	fmt.Println("version information: ", data)

	err = appConf.insert_file_header(data)
	if err != nil {
		log.Println(err)
		log.Println("create dfu file FAILURE!")
		return
	}
	log.Println("create dfu file OK!")
}

func (obj *NCPTargetVersion) Encode() ([]byte, error) {
	buf := new(bytes.Buffer)

	if err := binary.Write(buf, binary.BigEndian, obj); err != nil {
		return nil, err
	}

	return buf.Bytes(), nil
}

func bytes_extend(data []byte, size int) []byte {
	if size > len(data) {
		var ext []byte = make([]byte, size-len(data))
		var buffer bytes.Buffer
		buffer.Write(data)
		buffer.Write(ext)
		return buffer.Bytes()
	}
	return data
}

func (appConf AppConfig) insert_file_header(data []byte) error {
	context, err := ioutil.ReadFile(appConf.OtaFile)
	if err != nil {
		return err
	}

	var buf bytes.Buffer
	buf.Write(data)
	buf.Write(context)

	var newOtaFile string = appConf.NewOtaFileBaseName + "_" + fmt.Sprintf("%v", appConf.FileVersion.NCPVersion) + "_" + fmt.Sprintf("%v", appConf.FileVersion.SoftwareVersion) + "_" + fmt.Sprintf("%v", appConf.FileVersion.HardwareVersion) + filepath.Ext(appConf.OtaFile)
	os.Remove(newOtaFile)
	newFile, err := os.Create(newOtaFile)
	if err != nil {
		return err
	}

	buf.WriteTo(newFile)
	return nil
}

func (appConf *AppConfig) read_version() error {
	file, err := os.Open(appConf.VersionFile)
	if err != nil {
		return err
	}
	defer file.Close()

	data := bufio.NewReader(file)
	var count int = 0
	for count < 3 {
		line, _, err := data.ReadLine()
		if err == io.EOF {
			break
		}

		if appConf.read_version_from_str(string(line)) {
			count++
		}
	}

	if count == 3 {
		return nil
	}

	err = errors.New("read version from " + appConf.VersionFile + "failed")
	return err
}

func (appConf *AppConfig) read_version_from_str(line string) bool {
	if strings.Contains(line, "NCP_VERSION") {
		var define, ignore string
		var version uint16
		_, err := fmt.Sscanf(line, "%s %s 0X%x", &define, &ignore, &version)
		if err != nil {
			fmt.Sscanf(line, "%s %s 0x%x", &define, &ignore, &version)
		}

		if define == "#define" {
			log.Println("ncp version: ", version)
			appConf.FileVersion.NCPVersion = version
			return true
		}
	} else if strings.Contains(line, "SOFTWARE_VERSION") {
		var define, ignore string
		var version uint16
		_, err := fmt.Sscanf(line, "%s %s 0X%x", &define, &ignore, &version)
		if err != nil {
			fmt.Sscanf(line, "%s %s 0x%x", &define, &ignore, &version)
		}

		if define == "#define" {
			log.Println("software version: ", version)
			appConf.FileVersion.SoftwareVersion = version
			return true
		}
	} else if strings.Contains(line, "HARDWARE_VERSION") {
		var define, ignore string
		var version uint16
		_, err := fmt.Sscanf(line, "%s %s 0X%x", &define, &ignore, &version)
		if err != nil {
			fmt.Sscanf(line, "%s %s 0x%x", &define, &ignore, &version)
		}

		if define == "#define" {
			log.Println("hardware version: ", version)
			appConf.FileVersion.HardwareVersion = version
			return true
		}
	}

	return false
}
